﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Magiq.Support {
  public static class ExpressionExtensions {
    /// <summary>
    /// Retrieves the property represented by the specified expression
    /// </summary>
    /// <param name="expression">The expression.</param>
    /// <returns></returns>
    public static PropertyInfo ToPropertyInfo(this LambdaExpression expression) {
      var memberExpression = MemberExpression(expression);
      return (PropertyInfo) memberExpression.Member;
    }

    public static MethodInfo ToMethodInfo(this LambdaExpression expression) {
      var memberExpression = MethodCallExpression(expression);
      return memberExpression.Method;
    }

    public static MemberExpression MemberExpression(LambdaExpression expression) {
      return expression.Body.MemberExpression();
    }

    public static MethodCallExpression MethodCallExpression(this Expression expression) {
      MethodCallExpression methodCallExpression = null;
      switch (expression.NodeType) {
        case ExpressionType.Lambda: {
          var lambda = ((LambdaExpression) expression);
          methodCallExpression = lambda.Body as MethodCallExpression;
        }
          break;
      }

      if (methodCallExpression == null) throw new ArgumentException("Not a method call", "expression");
      return methodCallExpression;
    }

    public static ParameterExpression GetParameter(this Expression expression) {
      if (expression.Is<ParameterExpression>())
        return (ParameterExpression) expression;

      if (expression.Is<LambdaExpression>())
        return GetParameter(((LambdaExpression) expression).Body);
      if (expression.Is<MemberExpression>())
        return ((MemberExpression) expression).Expression.GetParameter();
      if (expression.Is<UnaryExpression>())
        return ((UnaryExpression) expression).Operand.GetParameter();
      if (expression.Is<MethodCallExpression>())
          return ((MethodCallExpression)expression).Object.GetParameter();
      if (expression.Is<BinaryExpression>()) {
        var binary = (BinaryExpression) expression;
        return binary.Left.GetParameter() ?? binary.Right.GetParameter();
      }
      return null;
    }

    public static LambdaExpression PreviousExpressionChain(this LambdaExpression self) {
      return self.PreviousExpressionChain(false);
    }

    public static LambdaExpression PreviousExpressionChain2(this LambdaExpression self, bool includeRoot) {
      var memberExpression = self.Body.MemberExpression();
      return !includeRoot && (memberExpression == null || memberExpression.Expression is ParameterExpression)
               ? null
               : Expression.Lambda(memberExpression.Expression, self.Parameters.ToArray());
    }

    public static LambdaExpression PreviousExpressionChain(this Expression self, bool includeRoot) {
      Expression e = null;
      if (self is LambdaExpression)
        return ((LambdaExpression) self).Body.PreviousExpressionChain(includeRoot);

      if (self is UnaryExpression)
        return ((UnaryExpression) self).Operand.PreviousExpressionChain(includeRoot);

      if (self is MemberExpression)
        e = ((MemberExpression) self).Expression;

      if (e == null || (!includeRoot && e is ParameterExpression))
        return null;

      return Expression.Lambda(e, self.GetParameter());
    }


    public static MemberExpression MemberExpression(this Expression self) {
      if (self is LambdaExpression)
        return ((LambdaExpression) self).Body.MemberExpression();

      if (self is UnaryExpression)
        return ((UnaryExpression) self).Operand.MemberExpression();

      if (self is MemberExpression)
        return (MemberExpression) self;

      return null;
    }

    private static Expression ConcreteExpression(this Expression self) {
      if (self is LambdaExpression)
        return ((LambdaExpression) self).Body.ConcreteExpression();

      if (self is UnaryExpression)
        return (((UnaryExpression) self).Operand.ConcreteExpression());

      if (self is MemberExpression)
        return ((MemberExpression) self).Expression.ConcreteExpression();

      return self;
    }

    public static bool IsConstant(this Expression self) {
      return self.ConcreteExpression() is ConstantExpression;
    }

    public static LambdaExpression ChangeParameters(this LambdaExpression self, params ParameterExpression[] parameters) {
      return Expression.Lambda(Expression.Invoke(self, parameters.Cast<Expression>()), parameters);
    }
  }
}